﻿//#define REPP_VERBOSE

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Windows.Markup;

using agree.schema;

using alib;
using alib.Character;
using alib.Collections;
using alib.Debugging;
using alib.Enumerable;
using alib.Observable;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Execute a REPP scenario to tokenize a string into YY-ish tokens. The tokens do not have any lattice vertices
	/// assigned.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ReppExecutor : m_observable<IString, SourceWord>,
		ρρReppRoot,
		ρρSourceItem
	{
		public ReppExecutor(IRtParent parent, IString sentence)
			: base(parent, sentence)
		{
		}

		///////////////////////////////////////////////////////////////////////
		///
		public IString SourceItem
		{
			get { return base.t0; }
			set { base.t0 = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		///////////////////////////////////////////////////////////////////////
		///
		ReppRootModule rr;
		public ReppRootModule ReppRoot
		{
			get { return this.rr; }
			set { this.rr = value; }
		}
		///
		///////////////////////////////////////////////////////////////////////


		protected override void start(IString sentence)
		{
			foreach (var tmt in rr.Tokenize(this, sentence))
				_item(tmt);

			_remove_task();
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	///
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class WhitespaceSplitterF : composition.f_primitive<IString, IdentArray<IWordToken>>
	{
		public WhitespaceSplitterF(IRtParent parent)
			: base(parent)
		{
		}

		protected override IdentArray<IWordToken> func(IString sentence)
		{
			var tokens = sentence.Text.Split(default(Char[]), StringSplitOptions.None);

			var arr = new IdentArray<IWordToken>(sentence, tokens.Length);

			for (int c, i_tok = 0, i_cur = 0; i_tok < tokens.Length; i_tok++)
			{
				var tok = tokens[i_tok];
				if ((c = tok.Length) > 0)
					arr[i_tok] = new WordToken(arr, tok, i_cur, i_cur + c, i_tok);

				i_cur += c + 1;
			}
			return arr;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public interface IReppItem
	{
		bool Process(ref String s);
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ReppEntryIgnore : IReppItem
	{
		public static ReppEntryIgnore Instance = new ReppEntryIgnore();
		private ReppEntryIgnore() { }

		public override String ToString() { return "Ignore"; }

		public bool Process(ref String s) { return false; }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ReppEntryRegex : Regex, IReppItem
	{
		static readonly Regex rx_repl;

		static ReppEntryRegex()
		{
			rx_repl = new Regex(@"\\([0-9]+)", RegexOptions.Compiled);
		}

		public ReppEntryRegex(ReppModule mod, String pattern, String replacement)
			: base(pattern, RegexOptions.Compiled)
		{
#if DEBUG
			this.mod = mod;
			if (pattern.Length > mod.regex_pattern_max)
				mod.regex_pattern_max = pattern.Length;
#endif
			this.repl = rx_repl.Replace(replacement, "$$$1");
		}

		String repl;

		public bool Process(ref String s)
		{
			var ss = s;
			s = base.Replace(s, repl);
			return s != ss;
		}

#if DEBUG
		ReppModule mod;

		public override String ToString()
		{
			var sp = pattern;
			return String.Format("RegEx   {0}   {1}", sp.PadRight(mod.regex_pattern_max + 2), repl);
		}
#endif
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class ReppModule : identity_named, IReppItem
	{
		public ReppModule(IIdentity prv)
			: base(prv)
		{
		}
		public ReppModule(IIdentity prv, String name)
			: base(prv, name)
		{
		}

		protected List<IReppItem> rgrx;
#if DEBUG
		public int regex_pattern_max;
#endif
		public bool IsEnabled { get; set; }

		Dictionary<int, ReppIterativeModule> iter_mods;

		protected bool IsLoaded { get { return rgrx != null; } }

		public void _load(ReppRootModule rp, TextReader tr, ref int line_no)
		{
			Debug.Assert(!IsLoaded);
			this.rgrx = new List<IReppItem>();
			String L;
			IReppItem re;
			while ((L = tr.ReadLine()) != null)
			{
				line_no++;
				if (L.Length == 0)
					continue;

				Char op = L[0];
				L = L.Substring(1);

				if ((re = rp.global_item(op, L)) != null)
				{
					rgrx.Add(re);
					continue;
				}

				switch (op)
				{
					case ';':
						continue;

					case '!':
						{
							var rg = L.Split(Charset.tab, StringSplitOptions.RemoveEmptyEntries);
							if (rg.Length > 2)
								throw new ReppException("REPP: '!' line must have only two parts separated by one or more tab characters. Line {0}", line_no);
							String repl = rg.Length == 1 ? String.Empty : rg[1];
							var regex = new ReppEntryRegex(this, rg[0], repl);

							rgrx.Add(regex);
						}
						break;

					case '>':
						{
							/// non-iterative modules do not reach this point; they are intercepted prior to this case statement. Integer-naming 
							/// is obligatory for and entails an iterative module.
							int iter_num;
							ReppIterativeModule iter_mod;
							if (iter_mods == null || !int.TryParse(L, out iter_num) || !iter_mods.TryGetValue(iter_num, out iter_mod))
								throw new ReppException("REPP: '>{0}' module not defined", L);

							/// note that these iterative sections must be defined before they are referenced.
							rgrx.Add(iter_mod);
						}
						break;

					case '<':
						{
							String s_try;
							if (!File.Exists(s_try = Path.Combine(rp.dir, L)))
								if (!File.Exists(s_try = Path.ChangeExtension(s_try, "rpp")))
									if (!File.Exists(s_try = Path.ChangeExtension(s_try, "repp")))
										throw new FileNotFoundException("REPP file not found", L);

							/// for this type of include file, temporarily switch to the new input file but don't create a new module;
							/// just continue appending to this current module.
							using (var sr2 = new StreamReader(s_try))
								_load(rp, sr2, ref line_no);
						}
						break;

					case '#':
						{
							if (L.Length == 0)
							{
								Debug.Assert(this is ReppIterativeModule);
								/// close iterative section
								return;
							}

							int iter_num;
							if (!int.TryParse(L, out iter_num))
								throw new ReppException("REPP: '#' must be followed by an integer");

							if (iter_mods == null)
								iter_mods = new Dictionary<int, ReppIterativeModule>();
							else if (iter_mods.ContainsKey(iter_num))
								throw new ReppException("REPP: '>{0}' iterative module already defined", L);

							var im = new ReppIterativeModule(this, iter_num);
							/// iterative section creates a new module, but reads from the same stream, consuming part of
							/// its parent's REPP code.
							im._load(rp, tr, ref line_no);
							this.iter_mods.Add(iter_num, im);
						}
						break;

					default:
						throw new ReppException("Unexpected: '{0}'", op);
				}

				//Console.WriteLine(L);
			}
		}

		public virtual bool Process(ref String s)
		{
#if DEBUG && REPP_VERBOSE
			Console.WriteLine("========= module {0} start ===========", this.Name);
#endif
			bool f_any = false;
			foreach (var rx in rgrx)
			{
				var repm = rx as ReppModule;
				if (repm != null && !repm.IsEnabled)
					continue;

				String ss = s;
				var b = rx.Process(ref s);
#if DEBUG && REPP_VERBOSE
				Debug.Assert(b == (s != ss));
				if (b)
					Console.WriteLine("{0}\r\n [{1}] -> [{2}]", rx, ss, s);
#endif
				f_any |= b;
			}
#if DEBUG && REPP_VERBOSE
			Console.WriteLine("========= module {0} end: {1} ===========", this.Name, f_any);
#endif
			return f_any;
		}

#if REPP_VERBOSE
		public override String ToString()
		{
			if (rgrx == null)
				return "not loaded";
			StringBuilder sb = new StringBuilder();
			String n = base.Name;
			if (n == null && this is ReppIterativeModule)
				n = ((ReppIterativeModule)this).num.ToString();
			String sz = String.Format("v{0} {1} \"{2}\" {0}v", new String('=', regex_pattern_max / 2), this.GetType().Name, n);
			sb.AppendLine(sz);

			foreach (var _l in rgrx.SelectMany(iri => iri.ToString().Split(nl_sep, StringSplitOptions.None)))
			{
				sb.AppendLine("|    " + _l);
			}

			sb.AppendFormat("^{0} end {0}^", new String('=', (sz.Length - 7) / 2));
			return sb.ToString();
		}
#endif
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public sealed class ReppIterativeModule : ReppModule
	{
		public ReppIterativeModule(IIdentity prv, int num)
			: base(prv)
		{
			this.num = num;
			base.IsEnabled = true;
		}
		public int num;

		public override bool Process(ref String s)
		{
			if (!base.Process(ref s))
				return false;

			while (base.Process(ref s))
				;
			return true;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	///
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Content")]
	[UsableDuringInitialization(true)]
	public sealed class ReppRootModule : ReppModule
	{
		public ReppRootModule(IIdentity prv)
			: base(prv)
		{
			this.modules = new Dictionary<String, ReppModule>(StringComparer.InvariantCultureIgnoreCase);
		}

		public String version;
		public Regex separator;
		public Dictionary<String, ReppModule> modules;
		public String dir;

		Object _content;
		public Object Content
		{
			get
			{
				if (_content as String == null)
				{
					var rf = _content as ReadFile;
					if (rf != null)
					{
						if (this.Name == null)
							this.Name = Path.GetFileNameWithoutExtension(rf.Filename);
						this.dir = Path.GetDirectoryName(rf.Filename);
					}
					_content = language_obj_utils.recover_string_content(_content);
				}
				return _content;
			}
			set
			{
				if (_content == null && value != null)
				{
					_content = value;
					String s_repp = (String)this.Content;
					using (StringReader sr = new StringReader(s_repp))
						add_module(this, sr);

					//Console.WriteLine(this.ToString());
				}
			}
		}

		public String Calls
		{
			get { return modules.Values.Where(rm => rm.IsEnabled).Select(rm => rm.Name).StringJoin("; "); }
			set
			{
				foreach (var s_rm in value.Split(Charset.semi_ws, StringSplitOptions.RemoveEmptyEntries))
				{
					ReppModule rm;
					if (!modules.TryGetValue(s_rm, out rm))
						throw new ReppException("REPP: module '{0}' not found", s_rm);
					rm.IsEnabled = true;
				}
			}
		}

		public
		IEnumerable<SourceWord> tokenize_raw(IIdentity prv, String source_text)
		{
			String s_mod = source_text;
			Process(ref s_mod);

			if (StringComparer.InvariantCultureIgnoreCase.Compare(source_text, s_mod) == 0)
				Console.WriteLine("REPP todo...");

			var tokenized = separator.Split(s_mod).Where(tok => tok.Length > 0).ToArray();

			var rejoined = tokenized.SelectMany(_s => _s.ToCharArray()).ToArray();

			/// locate the standoff positions (offsets in the original string) implied by the REPP tokenization

			var alignment = alib.EditDistance.EditDist.EditDistance(source_text.ToCharArray(), rejoined, (a, b) => 2.0);
			alignment.FindBacktrace();
			var lu = alignment.path
						.Where(pe => pe.tgt.Length > 0)
						.GroupBy(pe => pe.i_tgt, pe => pe.i_src)
						.ToDictionary(pe => pe.Key, pe => pe.First());

			int i = 0;
			foreach (var w in tokenized)
			{
				int i_start = lu[i];
				int i_end = lu[i + (w.Length - 1)] + 1;

				var source_word = source_text.Substring(i_start, i_end - i_start);

				yield return new SourceWord(prv, w, source_word, i_start, i_end);

				i += w.Length;
			}
		}

		public IEnumerable<SourceWord> Tokenize(IIdentity prv, IString istr)
		{
			return tokenize_raw(prv, istr.Text);
		}

		public void add_module(ReppModule mod_to_add, TextReader tr)
		{
			int line_no = 0;
			mod_to_add._load(this, tr, ref line_no);
			modules.Add(mod_to_add.Name, mod_to_add);
		}

		public IReppItem global_item(Char op, String L)
		{
			switch (op)
			{
				case '@':
					if (version != null)
						throw new ReppException("REPP: '@' already specified");
					version = L.Trim(new[] { '\t', ' ', '$' });
					Console.WriteLine("REPP " + version);
					return ReppEntryIgnore.Instance;

				case ':':
					if (separator != null)
						throw new ReppException("REPP: ':' token separator already specified");
					if (L.Length == 0)
						throw new ReppException("REPP: no token separator specified");
					separator = new Regex(L);
					return ReppEntryIgnore.Instance;

				case '>':
					int iter_num;
					if (!int.TryParse(L, out iter_num))
					{
						String s_try;
						FileInfo fi;

						if ((fi = DirectoryPathSet.FindFileInfo(this, s_try = Path.Combine(dir, L))) != null)
							goto ok;
						if ((fi = DirectoryPathSet.FindFileInfo(this, Path.ChangeExtension(s_try, "rpp"))) != null)
							goto ok;
						if ((fi = DirectoryPathSet.FindFileInfo(this, Path.ChangeExtension(s_try, "repp"))) != null)
							goto ok;

						throw new FileNotFoundException("REPP file not found", L);

					ok:
						//String s_mod = Path.GetFileNameWithoutExtension(L);
						if (modules.ContainsKey(L))
							throw new ReppException("REPP: a module called '{0}' has already been defined", L);

						ReppModule rm = new ReppModule(this, L);
						using (var sr2 = new StreamReader(fi.OpenRead()))
							add_module(rm, sr2);
						return rm;
					}
					/// assumes: any module name which has the form of an integer *must* be an iterative sub-module.
					break;
			}
			return null;
		}
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	///									Examples of YY input tokens
	///	Ulrich Schäfer (2005) Heart of Gold User and Developer Documentation. DFKI, Saarbrücken, 2005. 
	///	
	///				anaylsis is supplied	requesting morphology and		Named Entity with 
	///										lexicon access					type name
	///				======================  ==============================  ===================
	/// id			74						74								93
	///	start		12						12								3
	///	end			13						13								6
	///	path		1						1								1
	//	link		<115:121>				<115:121>
	///	form		"fisch"					"Fische"						"$NE_Loc"
	///	surface		"Fische"												"New York"
	///	ipos		0						0								0
	///	lrule		inflr-pl-nom-sg-dat		null							zero
	///	{pos p}+	"PROPER_NOUN" 0.91\		"PROPER_NOUN" 0.91\				"PROPER_NOUN" 1.0
	///				"VERB_IMPERATIV" 0.09	"VERB_IMPERATIV" 0.09	
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	[ContentProperty("Content")]
	[UsableDuringInitialization(true)]
	public sealed class YyRaw : identity_named
	{
		public YyRaw(IIdentity prv)
			: base(prv)
		{
		}

		public String Content { get; set; }
	};
}
